<?php namespace lfy;

//! \defgroup MiscFunctions Miscellaneous
/* @{ */

###	TIME & DATE

//! Microtime as formatted string to 6 decimal places
//!	@return string

	function utime($spf=false){
		$t = explode(' ', \microtime());
		$t = $t[1] + $t[0];
		return ($spf) ? sprintf('%01.6f', $t) : $t;
	}

	function stopwatch($start=LFY_TIME_IN, $prec=4){
		return sprintf('%01.'.$prec.'f', (utime() - $start) );
	}
	
###	ARRAYS

//!	Picks array member if present, returns null if not
//! \param k key to look for
//!	\param a array to search
//!	@return mixed
	
	function array_pick($k, $a){
		return isset($a[$k]) ? $a[$k] : null;	
	}
	
###     NUMBERS

    function num_pos($num){
            return ($num > 0) ? $num : ($num *-1);
    }
    
    function num_neg($num){
            return ($num < 0) ? $num : ($num *-1);
    }
    
    function num_compare($num1, $num2){
            return num_pos($num1 - $num2);
    }
    
    function big_get($bytes){
            return hexdec(bin2hex($bytes));
    }
    
    function lit_get($bytes){
            return hexdec(bin2hex(strrev($bytes)));
    }


/* @} */

### STRINGS

//! \defgroup StringFunctions String Functions
/* @{ */

//! mimics javascript indexOf
//!	\param hay to look through
//!	\param n needle to look for
//!	@return int

	function indexof($hay, $n){
		return is_integer($i = strpos($hay, $n)) ? $i : -1;	
	}
	
//! mimics javascript lastIndexOf
//!	\param hay to look through
//!	\param n needle to look for
//!	@return int

	function lastindexof($hay, $n){
		return is_integer($i = strrpos($hay, $n)) ? $i : -1;	
	}

	function str_contains($hay, $n){
		return (indexof($hay, $n) > -1);
	}
	
//!	unexplode explodes and re-implodes after filtering out empty members
//! \param s string to explode and re-implode
//!	\param d delimiter
//!	@return string

	function unexplode($s, $d='/'){
		return implode($d, array_filter(explode($d, $s)));
	}
	
//!	checks if hay begins with needle n
//!	@return bool

	function beginswith($hay, $n){
		return (substr($hay, 0, strlen($n)) == $n);	
	}
	
//! checks if hay ends with needle n
//!	@return bool

	function endswith($hay, $n){
		return (substr($hay, strlen($n)*-1) == $n);	
	}
	
//! checks if string s begins with b and ends with e (or b if e not provided)
//! @return bool

	function hasparenthesis($s, $b, $e=null){
		return (beginswith($s, $b) && endswith($s, (!is_null($e) ? $e : $b)));
	}
	
//!	returns prefix of string s where glue g delimits. 
//!	if g not present, returns empty string unless r passed as true, returns whole input string
//!	@return string

	function prefix($s, $g='/', $r=false){
		if(!$i = indexof($s, $g)) return '';
		return ($i == -1) ? ($r ? $s : '') : substr($s, 0, $i);	
	}
	
//! returns suffix of s where g delimits. Same rules as prefix()
//! @return string

	function suffix($s, $g='.', $r=false){
		$i = lastindexof($s, $g);
		return ($i == -1) ? ($r ? $s : '') : substr($s, $i+strlen($g));
	}

//! just like PHP's json_decode, but with error management
//! @return mixed

	function json_decode($string, $assoc=false, $depth=512, $on_fail=E_USER_WARNING){
		
		$json = \json_decode($string, $assoc, $depth);
		
		if(is_null($json)){
			
			$e = json_last_error();
			
			if($e == JSON_ERROR_NONE) // null is correct
				return null;
					
			$m = "Failed to decode json string: ";
				
			foreach(array('JSON_ERROR_DEPTH', 'JSON_ERROR_CTRL_CHAR', 'JSON_ERROR_SYNTAX') as $F)
				if(constant($F) | $e)
					$m .= "$F ";
					
			back_error($m, $on_fail);
		}
		return $json;
	}

//! tells us if json_decode() managed to parse its last string
//! @return bool

	function json_success(){
		return (json_last_error() == JSON_ERROR_NONE); 
	}

//!	Fairly efficient random string generator
//!	\param l desired length
//!	@return string

    function rand_string($l=32){
		$s = '';
		// get a nice big float for mt_rand max
		$m = (float) str_replace(array('.', ' '), '', \microtime());
		// make string equal to or bigger than $l
        while(($y = strlen($s)) < $l) $s .= base_convert(mt_rand($y, $m), 10, 36);
        // any excess, trim from random position at front
        if($n = ($y - $l)) $n = ($n - rand(0, $n));
        return substr($s, $n, $l);
    }

//!	Create an SHA512 salt for PHP's crypt
//!	\param r rounds to perform
//!	\param l desired length
//!	@return string

	function crypt_salt_sha512($r=5000, $l=32){
		return '$6$rounds='.$r.'$'.rand_string($l).'$';
	}

	function bytes_format($a, $i=true) {
		$d = $i ? 1024 : 1000;
		$s = $i ? "iB" : "B";
		$unim = array("B","K$s","M$s","G$s","T$s","P$s");
		$c = 0;
		while ($a>=$d) {
		    $c++;
		    $a = $a/$d;
		}
		return number_format($a, ($c ? 2 : 0))." ".$unim[$c];
	}


###	OUTPUT
	
	class default_verbosity_handler{
		function say($str, $level=1){
			if($level >= $GLOBALS['lfy\verbosity-level'])
				echo $str;
		}
	}

	$GLOBALS['lfy\verbosity-level'] = 0;
	$GLOBALS['lfy\verbosity-handler'] = new default_verbosity_handler;
	
	function set_verbosity_handler($h){
		$GLOBALS['lfy\verbosity-handler'] = $h;
	}
	
	function verbosity_level($n=null){
		$l = $GLOBALS['lfy\verbosity-level'];
		if(!is_null($n))
			$GLOBALS['lfy\verbosity-level'] = (int) $n;
		return $l;
	}
	
	function say($str, $level=1){
		$GLOBALS['lfy\verbosity-handler']->say($str, $level);
	}


/* @} */
		
###	PATHS 

//! \defgroup PathFunctions Path Functions
//!	Methods that deal with file and class path strings
/* @{ */

//!	Cleans REQUEST_URI u, stripping query string if present
//!	@return string

	function strip_request_uri($u){
		return urldecode(unexplode(prefix($u, '?', true)));
	}

	
    function class_basename($class){
            if(!is_string($class)) $class = get_class($class);
            return suffix($class, '\\', true);
    }

//!	Turns filepath relative to project directory into full path.
//!	If filepath is already full then returns unchanged
//!	\param f filepath
//!	@return string

	function project_file($f){
		return beginswith($f, '/') ? $f : LFY_PROJECT_PATH."/$f";
	}



/* @} */

###	SUPERGLOBALS

//! \addtogroup MiscFunctions
/* @{ */


//!	plucks k from GET array or returns null
//!	@return mixed

	function QS($k){
		return array_pick($k, $_GET);
	}
	
//!	plucks k from POST array or returns null
//!	@return mixed

	function POST($k){
		return array_pick($k, $_POST);
	}
	
//!	plucks k from COOKIE array or returns null
//!	@return mixed

	function COOKIE($k){
		return array_pick($k, $_COOKIE);
	}
	
//!	plucks k from SERVER array or returns null
//!	@return mixed

	function SERVER($k){
		return array_pick($k, $_SERVER);
	}
	

	function php_http_headers(){
	
		static $a;
		
		if(is_null($a))
			foreach($_SERVER as $k=>$v)
				if(\lfy\beginswith($k, 'HTTP_'))
					$a[ str_replace('_', '-', substr($k, 5)) ] = $v;
						
		return $a;	
	}


	function request_headers(){
		
		static $a;

		if(is_null($a))
			foreach(\lfy\php_http_headers() as $k => $v)
				$a[ ucwords(strtolower($k)) ] = $v;
		
		return $a;			
	}


//!	plucks k from request headers or returns null
//!	@return mixed
	
	function REQ($n){
		return \lfy\array_pick(strtoupper($n), \lfy\php_http_headers());
	}

/* @} */
	
###	ENVIRONMENT VARIABLES

//! \addtogroup EnvVars LFY Environment Variables
/* @{ */

	$GLOBALS['lfy\envcache'] = array();

//!	Returns either environment variable, constant or null, in that order.
//!	Env vars and constants may be set to json arrays ['a','b']
//!	\param N name of environment variable / constant
//!	@return mixed

	function getenv($N){
		if(!$v = array_pick($N, $GLOBALS['lfy\envcache'])){
			// unset/undefined env vars are returned as null
			$v = (($e = \getenv($N)) !== false) ? readenv($e) : (!defined($N) ? null : readenv(constant($N)));
			if(!is_null($v)) $GLOBALS['lfy\envcache'][$N] = $v;
		}
		return $v;
	}

	function setenv($N, $v){
		$is = is_string($v);
		$GLOBALS['lfy\envcache'][$N] = !$is ? $v : readenv($v);
	}
	
	function setconst($N, $V){
		if(!defined($N)) define($N, $V);
	}
	
//!	reads our env var syntax for getenv(). expects string as arg
//! @return mixed

	function readenv($e){
		if(is_numeric($e)) return (float)$e;
		if(!$e) return $e;
		if(beginswith($e, '=csv:')) return explode(',', substr($e, 5));
		if(beginswith($e, '=json:')) return json_decode(substr($e, 6));
		return $e;
	}

	function writeenv($e, $quote=false){
		if(is_numeric($e)) return (float)$e;
		if(is_bool($e)) return (int)$e;
		if(is_string($e)) return $quote ? '"'.$e.'"' : $e;
		if(is_array($e)){
			$csv = true;
			foreach($e as $k=>$v){
				if(!is_integer($k) || !is_scalar($v)){
					$csv = false;
					break;
				}
				if(is_string($v) && (str_contains($v, ','))){
					$csv = false;
					break;
				}
			}
			if($csv){
				$e = '=csv:'.implode(',', $e);
				return $quote ? '"'.$e.'"' : $e;	
			}
		}
		$e = '=json:'.json_encode($e);
		return $quote ? '"'.$e.'"' : $e;	
	}

/* @} */
	
###	ERROR HANDLING

//! \addtogroup ErrorHandling LFY Error Handling
/* @{ */

//!	mimics PHP's error_reporting fuction, but for logging
//!	\param n level to set to
//!	\return int previous log level

	function error_logging($n=null){
		$o = getenv('LFY_ERROR_LOGGING');
		if(!is_null($n)) putenv("LFY_ERROR_LOGGING=$n");	
		return $o;
	}
	

//!	allows us to pluck an error culprit from backtrace.
//!	very handy inside a function when you want to know what file sent the bad input
//!	\param m message
//!	\param e error severity
//!	\param b int how far back to trace. Will actually trace back to b + 1

	function back_error($m, $e=E_NOTICE, $b=0, $n=false){
		$a = array_pick($b+1, debug_backtrace());
		if(!$n){		
			if($c = array_pick('class', $a)) $c .= array_pick('type', $a);
			if($f = array_pick('function', $a)) $f .= '()';
			$n = "$c$f";
		}
		error::handle($e, "$n $m", $a['file'], $a['line']);
	}

/* @} */
	
###	PROCEDURE EVENT HANDLING

	$GLOBALS['lfy\events'] = array();

//!	registers functions to be fired on given event.
//!	lfy fires 3 events in succession on shutdown: 'eof', 'shutdown' and 'exit'.
//!	other events maybe defined and fired manually.
//!	\param repeatable method is repeatable or not

	function on_event($event, $func, $repeatable=false){
		$GLOBALS['lfy\events'][$event][] = array($func, $repeatable);	
	}

//!	fires named event
	
	function fire_event($event){
		if(!is_array($a = &$GLOBALS['lfy\events'][$event])) return;
		foreach($a as $i=>$fa){
			call_user_func($fa[0]);
			if(!$fa[1]) unset($a[$i]);
		}	
	}
	
###	CLASS INSTANTIATION

//! \defgroup ObjectFunctions Class Instantiation Helper Functions
/* @{ */

//!	class autoload function registered to spl_autoload

	function autoload($x){
		$a = array_filter(explode('\\', $x));
		$f = LFY_NAMESPACE_PATH.'/'.implode('/', $a).'.php';
		if(!\lfy\getenv('LFY_SKIP_FSTAT') && !is_file($f)) return;
		require $f;
	}


/* @} */

	define('LFY_USE_CONFIG', LFY_CLI_MODE);

	function loadconfig(){
	
		$conf = array();

		if(\lfy\getenv('LFY_USE_CONFIG')){
		
			ob_start();
			@include LFY_NAMESPACE_PATH."/lfy.conf";
			$arr = @parse_ini_string(ob_get_clean(), true);
		
			if(is_array($arr)){
		
				foreach($arr as $k=>$v){
					if(!strpos($k, ':')){
						$conf[strtoupper($k)] = $v;
						unset($arr[$k]);
					}
				}
			
				$modal_additions = function($prefix, &$arr, &$conf){
					$prefix .= ':';
					foreach($arr as $k=>$v){
						if(\lfy\beginswith($k, $prefix)){
							$KEY = strtoupper(substr($k, strlen($prefix)));
							if(isset($conf[$KEY]) && is_array($conf[$KEY]))
								$v = array_merge($conf[$KEY], $v);
							$conf[$KEY] = $v;
							unset($arr[$k]);
						}
					}	
				};
		
				$modal_additions(LFY_CLI_MODE ? 'CLI' : 'WWW', $arr, $conf);
			
				if($mode = \lfy\getenv('LFY_USER_MODE'))
					$modal_additions($mode, $arr, $conf);
		
				if(is_array(@$conf['LFY']))
					foreach($conf['LFY'] as $k=>$v)
						\lfy\setconst(strtoupper("LFY_$k"), $v);
				
				if(is_array(@$conf['PHP']))
					foreach($conf['PHP'] as $k=>$v)
						ini_set($k, $v);
				
				if(is_array(@$conf['ENV']))
					foreach($conf['ENV'] as $k=>$v)
						\lfy\setenv($k, $v);
				
				if(is_array(@$conf['ENUM']))
					foreach($conf['ENUM'] as $k=>$v)
						\lfy\setconst($k, $v);
			
			}
			
		}
		
		if(!defined('LFY_PROJECT_PATH'))
			define('LFY_PROJECT_PATH', LFY_NAMESPACE_PATH);
			
		if(!defined('LFY_PROJECT'))
			define('LFY_PROJECT', basename(LFY_PROJECT_PATH));
		
		if(!defined('LFY_PROJECT_LOWER'))	
			 define('LFY_PROJECT_LOWER', preg_replace("/[^a-z0-9]/", '-', strtolower(LFY_PROJECT)));
			 
		if(!defined('LFY_PROJECT_UPPER'))
			 define('LFY_PROJECT_UPPER', preg_replace("/[^A-Z0-9]/", '_', strtoupper(LFY_PROJECT)));
		
		$GLOBALS['lfy\config'] = $conf;
		
		return $conf;
		
	}


###	MEMCACHED

//!	Versatile but simple memcached implementation.
//! Enables simple separation and bunching of data stores by 
//!	allowing $persistent_id to also denote both a key prefix and a server set.  
//! If no matching server set is found in defaults.php then the default 'lfy' set is used
//!	\todo implement this properly
//! @returns Memcached
	
	function mcached( $persistent_id = default_memcached_set ) {

		// one instantiation per-connection per-request
		static $memcached_instances = array();
	
		if( array_key_exists($persistent_id, $memcached_instances)) {

			$instance = $memcached_instances[$persistent_id];

		}else{

			$instance = new \Memcached($persistent_id);

			// Add servers if no connections listed. Get server set by $persistent_id or use default set.
			// In a production environment with multiple server sets you may wish to prevent typos from silently adding data 
			// to the default pool, in which case return an error on no match instead of defaulting
			if( !count($instance->getServerList()) ) {
			
				$instance->setOption(\Memcached::OPT_PREFIX_KEY, "lfy$persistent_id:");
				$instance->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true); // advisable option
			
				$servers = array_key_exists($persistent_id, $GLOBALS['lfy\\memcached-sets'])
					? $GLOBALS['lfy\\memcached-sets'][$persistent_id]
					: $GLOBALS['lfy\\memcached-sets'][LFY_DEFAULT_MEMCACHED_SET];
				$instance->addServers($servers);
			}

			$memcached_instances[$persistent_id] = $instance;
		}
		return $instance;
	}
	

###	SIMPLE BASE CLASSES

//!	simple base class

	class o{	

		function get($k){
			return isset($this->$k) ? $this->$k : null;
		}

		function find(){
			foreach(func_get_args() as $k){
				if(isset($this->$k)) return $k;
			}
		}
		
	}
	

//!	simple base class extended. May be instantiated with array or object to inherit members from. 

	class obj extends o{
		
		function __construct($array=false){
			if($array) $this->add($array);
		}

		function set($k, $v){
			$this->$k = $v;
		}

		// unset is a reserved keyword
		function del($k){
			unset($this->$k);
		}

		function add($a){
			foreach($a as $k=>$v) $this->$k = $v;
		}
	}


//! Provides a static variable specific to the called class, hidden within a method.

	class schematic{
	
		//! data to store in our schema on instantiation
	
		protected $schema_set;
	
		function __construct(){
			if(is_array($this->schema_set))
				$this->schema()->add($this->schema_set);
			unset($this->schema_set);
		}
	
		//! fetch our stored schema, from static or instantiated contexts
		//!	@return \ref obj
	
		public static function schema ($create = true){
			static $schema = array();
			$c = get_called_class();
			if(!isset($schema[$c])){
				if(!$create) return false;
				$schema[$c] = new obj;
			}
			return $schema[$c];
		}
	
	}
	

//! As schematic, but protected member vars are readable through overloading
	
	class schematic_ro extends schematic{
	
		function __get($k){
			if(isset($this->$k)) return $this->$k;
		}	
	
	}	


//!	stores its instance in its schema

	class singleton extends schematic{
	
		//! fetch our instance or instantiate
		//! @return \ref singleton
	
		public static function instance(){
			$class = get_called_class();
			$schema = $class::schema();
			if(!($schema->get('instance') instanceof $class)) 
				$schema->set('instance', new $class);
			return $schema->instance;
		}
	
		//! makes unlinking of symbol possible.
		//! static or dynamic
	
		public static function trash_instance(){
			$class = get_called_class();
			if($schema = $class::schema(false)) 
				$schema->del('instance');
		}
		
		public static function has_instance(){
			$class = get_called_class();
			return (
				($schema = $class::schema(false)) && 
				($inst = $schema->get('instance')) &&
				($inst instanceof $class)
			);
		}
	
	}
	
	
//! As singleton, but protected member vars are readable through overloading
	
	class singleton_ro extends singleton{
	
		function __get($k){
			if(isset($this->$k)) return $this->$k;
		}	
	
	}

